﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	/// <summary>
	/// This class represents any call operation (virtual, static, or via a function pointer).
	/// </summary>
	public sealed class IRCallOp : IRByteCode
	{
		/// <summary>
		/// The declared return type of the call.
		/// </summary>
		private readonly IRType m_restype;

		/// <summary>
		/// If this is a CALL (not a CALLVIRT) then this is the restricted type for the call (null otherwise).
		/// </summary>
		private readonly IRType m_specificTarget;

		/// <summary>
		/// The type that is provided by the .net compiler for the type that defines the method 
		/// (i.e. introduces the definition). 
		/// Generally the <c>m_reciverexptype</c> we extract below is more precise but this is useful 
		/// when resolving calls with explicit implementations.
		/// </summary>
		private readonly IRType m_baseimpltype;

		/// <summary>
		/// The set of all call targets that we resolve in the static analysis.
		/// </summary>
		private readonly HashSet<IRInvokeIdentity> m_seencalltargets;

		/// <summary>
		/// If this is a virtual method then this is the type of the this parameter (null otherwise).
		/// </summary>
		private readonly IRType m_reciverexptype;
		
		/// <summary>
		/// The signature of the invocation.
		/// </summary>
		private readonly IRInvokeSig m_invs;

		/// <summary>
		/// Return true if the return value from this call is the get current for a loop traversal.
		/// </summary>
		public bool IsLoopTraverseGet;

		/// <summary>
		/// If the traversal is on a container with then this is the root var 
		/// that we always access the container (or enumerator) from.
		/// </summary>
		public IRVarName ContainerCopyVar;

		/// <summary>
		/// If the traversal is on an container with an indexer then 
		/// this is the optional member field that we always access the container from.
		/// </summary>
		public IRMemberField OptContainerCopyField;

		/// <summary>
		/// The unique operation that the indexer variable can me stored to at (i.e. check this)
		/// </summary>
		public IRStoreFromEvalStackBasic IndexerStoreLoc;

		/// <summary>
		/// True if this store updates the enumerator position for a loop traversal.
		/// </summary>
		public bool IsTraverseLoopIndexAdvance;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_restype != null);
			Contract.Invariant(this.m_invs != null);
			
			Contract.Invariant((this.OperationCode != OpCode.Op.Call) | (this.m_specificTarget != null));
			Contract.Invariant((this.OperationCode != OpCode.Op.CallVirt) | (this.m_baseimpltype != null));
			Contract.Invariant((this.OperationCode != OpCode.Op.CallVirt) | (this.m_reciverexptype != null));
		}

		private IRCallOp(OpCode.Op copc, IRType rtype, IRType baseimpltype, IRType reciverType, IRInvokeSig sig, IRType specTarget, IRSourceLocation sloc)
			: base(copc, sloc)
		{
			this.m_restype = rtype;
			this.m_baseimpltype = baseimpltype;
			this.m_reciverexptype = reciverType;
			this.m_invs = sig;
			this.m_specificTarget = specTarget;

			this.m_seencalltargets = new HashSet<IRInvokeIdentity>();

			this.IsLoopTraverseGet = false;
			this.ContainerCopyVar = null;
			this.OptContainerCopyField = null;
			this.IndexerStoreLoc = null;
		}

		/// <summary>
		/// Make a call to a static method. 
		/// </summary>
		public static IRCallOp MakeStaticMethodCall(IRType rtype, IRStaticInvoke sig, IRSourceLocation sloc)
		{
			Contract.Requires(rtype != null);
			Contract.Requires(sig != null);
			
			return new IRCallOp(OpCode.Op.Call, rtype, null, null, sig, sig.DefClass, sloc);
		}

		/// <summary>
		/// Make a virtual method call on a reciver argument of the given type and specialized to specTarget.
		/// </summary>
		public static IRCallOp MakeRestrictedVirtualCall(IRType rtype, IRType baseimpltype, IRType reciverType, IRVirtualInvoke sig, IRType specTarget, IRSourceLocation sloc)
		{
			Contract.Requires(rtype != null);
			Contract.Requires((baseimpltype != null) & (!(baseimpltype is IRPtrType)));
			Contract.Requires(reciverType != null);
			Contract.Requires(sig != null);
			Contract.Requires(specTarget != null);
			
			return new IRCallOp(OpCode.Op.Call, rtype, baseimpltype, reciverType, sig, specTarget, sloc);
		}

		/// <summary>
		/// Make a virtual method call on a reciver argument of the given type.
		/// </summary>
		public static IRCallOp MakeVirtualCall(IRType rtype, IRType baseimpltype, IRType reciverType, IRVirtualInvoke sig, IRSourceLocation sloc)
		{
			Contract.Requires(rtype != null);
			Contract.Requires((baseimpltype != null) & (!(baseimpltype is IRPtrType)));
			Contract.Requires(reciverType != null);
			Contract.Requires(sig != null);
			
			return new IRCallOp(OpCode.Op.CallVirt, rtype, baseimpltype, reciverType, sig, null, sloc);
		}

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder();

			if(this.OperationCode == OpCode.Op.CallVirt)
			{
				sb.Append(this.m_reciverexptype.ToString() + "@" + this.m_baseimpltype.ToString() + "." + this.m_invs.ToString());
			}
			else
			{
				sb.Append("(" + this.m_specificTarget.ToString() + ")" + this.m_invs.ToString());
			}

			if(this.IsLoopTraverseGet)
			{
				sb.Append(" Get-" + this.ContainerCopyVar.ToString());
				if(this.OptContainerCopyField != null)
					sb.Append("." + this.OptContainerCopyField.ToString());

				if(this.IndexerStoreLoc != null)
					sb.Append("[" + this.IndexerStoreLoc.LocalVar.ToString() + "]");
			}

			if(this.IsTraverseLoopIndexAdvance)
				sb.Append(" LoopAdvance");

			return sb.ToString();
		}

		/// <summary>
		/// Return true if the method has a specified target class to lookup the method definition in.
		/// </summary>
		public bool HasSpecificTarget 
		{ get { return this.m_specificTarget != null; } }

		/// <summary>
		/// If the method has a specified target class to lookup the method definition in return the type.
		/// </summary>
		public IRType SpecificTargetClass 
		{ 
			get 
			{
				Contract.Requires(this.HasSpecificTarget);
				
				return this.m_specificTarget; 
			} 
		}

		/// <summary>
		/// If the call is a virtual invoke get the base type as given by the .net compiler 
		/// which defines the desired method.
		/// </summary>
		public IRType BaseImplementationType
		{
			get
			{
				Contract.Requires(this.InvokeSig.IsVirtualInvoke);
				
				return this.m_baseimpltype;
			}
		}

		/// <summary>
		/// If the call is a virtual invoke get the type of the reciver.
		/// </summary>
		public IRType ReciverExpType 
		{ 
			get 
			{
				Contract.Requires(this.InvokeSig.IsVirtualInvoke);
				
				return this.m_reciverexptype; 
			} 
		}

		/// <summary>
		/// Get the return type for the method call.
		/// </summary>
		public IRType ReturnType
		{ get { return this.m_restype; } }
		
		/// <summary>
		/// Return the signature for the method that is being invoked.
		/// </summary>
		public IRInvokeSig InvokeSig
		{ get { return this.m_invs; } }

		public HashSet<IRInvokeIdentity> AnalysisResolvedCallTargets
		{ get { return this.m_seencalltargets; } }
	}
}
